#!/usr/bin/env python

# Copyright 2012 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""OAuth 2.0 Authorization Server implementation of the Attribute Provider."""

import datetime
import logging
from google.appengine.ext import webapp
from lib import http
import model
from streetidentity_ap import utils

try:  # pragma: no cover
  import simplejson
except ImportError:  # pragma: no cover
  try:
    # Try to import from django, should work on App Engine
    from django.utils import simplejson
  except ImportError:
    # Should work for Python2.6 and higher.
    import json as simplejson

__author__ = 'Maciej Machulak'
__maintainer__ = 'Maciej Machulak'
__email__ = 'mmachulak@google.com'

__copyright__ = 'Copyright 2012, Google Inc.'
__license__ = 'Apache License 2.0'
__version__ = '0.2'
__status__ = 'Prototype'


class OAuthError(Exception):
  """OAuth Exception."""
  pass


class TokenHandler(webapp.RequestHandler):
  """OAuth 2.0 Token Endpoint."""

  def _process_error(self, status, error, error_description=None):
    error_response = {'error': error}
    if error_description:
      error_response['error_description'] = error_description
    self.response.set_status(status)
    self.response.out.write(simplejson.dumps(error_response))

  def _get_client_credentials(self):
    client_id = self.request.get('client_id')
    client_secret = self.request.get('client_secret')

    if not client_id or not client_secret:
      raise OAuthError('could not find client')

    return client_id, client_secret

  def post(self):
    """Processes OAuth 2.0 Token Requests."""

    logging.debug('request: %s', self.request)

    # Check grant type
    grant_type = self.request.get('grant_type')
    logging.debug('grant type: %s', grant_type)
    if not grant_type or (grant_type != 'client_credentials'
                          and grant_type != 'refresh_token'):
      self._process_error(400,
                          'invalid_grant',
                          'unknown grant type - must be client_credentials '
                          'or refresh_token')
      return

    # Check credentials
    # Get client credentials - from the body
    # TODO(mmachulak): obtain credentials from the header
    client_id, client_secret = self._get_client_credentials()

    application = model.Application.all().\
    filter('client_id = ', client_id).\
    filter('client_secret = ', client_secret).get()

    if application is None:
      self._process_error(400,
                          'invalid_client',
                          'could not authenticate client')
      return

    # Process either client_credentials or refresh_token
    token_response = {}

    issued_at = datetime.datetime.now()
    expires_in = datetime.timedelta(hours=1)
    expires_at = issued_at + expires_in

    if grant_type == 'client_credentials':
      # Check if there is already an authorization
      authorization = model.Authorization.all().filter('application = ',
                                                       application).get()

      if authorization:
        authorization.access_token = utils.random()
        authorization.iat = issued_at
        authorization.exp = expires_at
        authorization.put()
        token_response = {'access_token': authorization.access_token,
                          'expires_in': expires_in.seconds,
                          'token_type': authorization.token_type}
      else:
        authorization = model.Authorization(
            access_token=utils.random(),
            iat=issued_at,
            exp=expires_at,
            refresh_token=utils.random(),
            application=application
        )
        authorization.put()
        token_response = {'access_token': authorization.access_token,
                          'token_type': authorization.token_type,
                          'refresh_token': authorization.refresh_token,
                          'expires_in': expires_in.seconds}

    elif grant_type == 'refresh_token':
      refresh_token = self.request.get('refresh_token')

      # Check if there is already an authorization
      authorization = model.Authorization.all().filter('refresh_token = ',
                                                       refresh_token).get()

      if authorization:
        authorization.access_token = utils.random()
        authorization.iat = issued_at
        authorization.exp = expires_at
        authorization.put()
        token_response = {'access_token': authorization.access_token,
                          'token_type': authorization.token_type,
                          'expires_in': expires_in.seconds}
      else:
        self._process_error(400, 'invalid_client', 'refresh token is invalid')
        return

    logging.debug('returning token response')
    token_response_str = simplejson.dumps(token_response)
    logging.debug('response string %s', token_response_str)

    self.response.set_status(200)
    self.response.headers['Content-Type'] = 'application/json; charset=utf-8'
    self.response.out.write(token_response_str)


def api_authz_required():
  """Decorator for OAuth 2.0 protected resources."""

  def inner_api_authz_required(handler):

    def check_authz(self, *args):
      authz_header = self.request.headers.get(http.Header.authorization)
      if authz_header is None:
        logging.info('api call is not authorized')
        self.response.set_status(401)
        return
      logging.info('authz header: %s', authz_header)
      header_list = authz_header.split(' ')
      access_token = header_list[1]
      if access_token is None:
        logging.info('token is none')
        self.response.set_status(401)
        return

      application = _get_client_for_token(access_token)

      if application is None:
        logging.info('token is invalid')
        self.response.set_status(401)
        return
      self.application = application
      handler(self, *args)

    return check_authz

  return inner_api_authz_required


def _get_client_for_token(access_token):
  logging.debug('getting client for the token')
  authorization = model.Authorization.all().filter('access_token = ',
                                                   access_token).get()
  if authorization:
    logging.debug('found authorization for this token')

    now = datetime.datetime.now()
    logging.debug('current time: %s', now)
    logging.debug('token expiration time: %s', authorization.exp)

    if authorization.exp >= now:
      logging.debug('authorization still valid')
      return authorization.application

    logging.debug('authorization has expired')

  logging.debug('authorization could not be found')
  return None
